home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2008 February / PCWFEB08.iso / Software / Freeware / Miro 1.0 / Miro_Installer.exe / Miro_Downloader.exe / util.pyc (.txt) < prev    next >
Encoding:
Python Compiled Bytecode  |  2007-11-12  |  24.7 KB  |  797 lines

  1. # Source Generated with Decompyle++
  2. # File: in.pyc (Python 2.5)
  3.  
  4. import os
  5. import random
  6. import re
  7. import sys
  8. import sha
  9. import time
  10. import string
  11. import urllib
  12. import socket
  13. import logging
  14. import filetypes
  15. import tempfile
  16. import threading
  17. import traceback
  18. import subprocess
  19. from clock import clock
  20. from types import UnicodeType, StringType
  21. from BitTorrent.bencode import bdecode, bencode
  22. chatter = True
  23. inDownloader = False
  24. ignoreErrors = False
  25.  
  26. def quoteJS(x):
  27.     x = x.replace('\\', '\\\\')
  28.     x = x.replace('"', '\\"')
  29.     x = x.replace("'", "\\'")
  30.     x = x.replace('\n', '\\n')
  31.     x = x.replace('\r', '\\r')
  32.     return x
  33.  
  34.  
  35. def getNiceStack():
  36.     """Get a stack trace that's a easier to read that the full one.  """
  37.     stack = traceback.extract_stack()
  38.     while (len(stack) > 0 or os.path.basename(stack[0][0]) == 'unittest.py' or isinstance(stack[0][3], str)) and stack[0][3].startswith('unittest.main'):
  39.         stack = stack[1:]
  40.     for i in xrange(len(stack)):
  41.         if os.path.basename(stack[i][0]) == 'util.py' and stack[i][2] in ('failed', 'failedExn'):
  42.             stack = stack[:i + 1]
  43.             break
  44.             continue
  45.     
  46.     stack = _[1]
  47.     return stack
  48.  
  49.  
  50. def readSimpleConfigFile(path):
  51.     ret = { }
  52.     f = open(path, 'rt')
  53.     for line in f.readlines():
  54.         if re.match('^[ \t]*$', line):
  55.             continue
  56.         
  57.         match = re.match('^([^ ]+) *= *([^\\r\\n]*)[\\r\\n]*$', line)
  58.         if not match:
  59.             print "WARNING: %s: ignored bad configuration directive '%s'" % (path, line)
  60.             continue
  61.         
  62.         key = match.group(1)
  63.         value = match.group(2)
  64.         if key in ret:
  65.             print "WARNING: %s: ignored duplicate directive '%s'" % (path, line)
  66.             continue
  67.         
  68.         ret[key] = value
  69.     
  70.     return ret
  71.  
  72.  
  73. def writeSimpleConfigFile(path, data):
  74.     f = open(path, 'wt')
  75.     for k, v in data.iteritems():
  76.         f.write('%s = %s\n' % (k, v))
  77.     
  78.     f.close()
  79.  
  80.  
  81. def queryRevision(f):
  82.     
  83.     try:
  84.         p = subprocess.Popen([
  85.             'svn',
  86.             'info',
  87.             f], stdout = subprocess.PIPE)
  88.         info = p.stdout.read()
  89.         p.stdout.close()
  90.         url = re.search('URL: (.*)', info).group(1)
  91.         url = url.strip()
  92.         revision = re.search('Revision: (.*)', info).group(1)
  93.         revision = revision.strip()
  94.         return (url, revision)
  95.     except KeyboardInterrupt:
  96.         raise 
  97.     except:
  98.         return None
  99.  
  100.  
  101.  
  102. def absolutePathToFileURL(path):
  103.     if isinstance(path, unicode):
  104.         path = path.encode('utf-8')
  105.     
  106.     parts = string.split(path, os.sep)
  107.     parts = [ urllib.quote(x, ':') for x in parts ]
  108.     return 'file://' + '/'.join(parts)
  109.  
  110.  
  111. def failedExn(when, **kwargs):
  112.     failed(when, withExn = True, **kwargs)
  113.  
  114.  
  115. def failed(when, withExn = False, details = None):
  116.     logging.info('failed() called; generating crash report.')
  117.     header = ''
  118.     
  119.     try:
  120.         import config
  121.         import prefs
  122.         header += 'App:        %s\n' % config.get(prefs.LONG_APP_NAME)
  123.         header += 'Publisher:  %s\n' % config.get(prefs.PUBLISHER)
  124.         header += 'Platform:   %s\n' % config.get(prefs.APP_PLATFORM)
  125.         header += 'Python:     %s\n' % sys.version.replace('\r\n', ' ').replace('\n', ' ').replace('\r', ' ')
  126.         header += 'Py Path:    %s\n' % repr(sys.path)
  127.         header += 'Version:    %s\n' % config.get(prefs.APP_VERSION)
  128.         header += 'Serial:     %s\n' % config.get(prefs.APP_SERIAL)
  129.         header += 'Revision:   %s\n' % config.get(prefs.APP_REVISION)
  130.         header += 'Builder:    %s\n' % config.get(prefs.BUILD_MACHINE)
  131.         header += 'Build Time: %s\n' % config.get(prefs.BUILD_TIME)
  132.     except KeyboardInterrupt:
  133.         raise 
  134.     except:
  135.         pass
  136.  
  137.     header += 'Time:       %s\n' % time.asctime()
  138.     header += 'When:       %s\n' % when
  139.     header += '\n'
  140.     if withExn:
  141.         header += 'Exception\n---------\n'
  142.         header += ''.join(traceback.format_exception(*sys.exc_info()))
  143.         header += '\n'
  144.     
  145.     if details:
  146.         header += 'Details: %s\n' % (details,)
  147.     
  148.     header += 'Call stack\n----------\n'
  149.     
  150.     try:
  151.         stack = getNiceStack()
  152.     except KeyboardInterrupt:
  153.         raise 
  154.     except:
  155.         stack = traceback.extract_stack()
  156.  
  157.     header += ''.join(traceback.format_list(stack))
  158.     header += '\n'
  159.     header += 'Threads\n-------\n'
  160.     header += 'Current: %s\n' % threading.currentThread().getName()
  161.     header += 'Active:\n'
  162.     for t in threading.enumerate():
  163.         if not t.isDaemon() or ' [Daemon]':
  164.             pass
  165.         header += ' - %s%s\n' % (t.getName(), '')
  166.     
  167.     report = '{{{\n%s}}}\n' % header
  168.     
  169.     def readLog(logFile, logName = 'Log'):
  170.         
  171.         try:
  172.             f = open(logFile, 'rt')
  173.             logContents = '%s\n---\n' % logName
  174.             logContents += f.read()
  175.             f.close()
  176.         except KeyboardInterrupt:
  177.             raise 
  178.         except:
  179.             logContents = ''
  180.  
  181.         return logContents
  182.  
  183.     logFile = config.get(prefs.LOG_PATHNAME)
  184.     downloaderLogFile = config.get(prefs.DOWNLOADER_LOG_PATHNAME)
  185.     if logFile is None:
  186.         logContents = 'No logfile available on this platform.\n'
  187.     else:
  188.         logContents = readLog(logFile)
  189.     if downloaderLogFile is not None:
  190.         if logContents is not None:
  191.             logContents += '\n' + readLog(downloaderLogFile, 'Downloader Log')
  192.         else:
  193.             logContents = readLog(downloaderLogFile)
  194.     
  195.     if logContents is not None:
  196.         report += '{{{\n%s}}}\n' % stringify(logContents)
  197.     
  198.     logging.info('----- CRASH REPORT (DANGER CAN HAPPEN) -----')
  199.     logging.info(header)
  200.     logging.info('----- END OF CRASH REPORT -----')
  201.     if not inDownloader:
  202.         
  203.         try:
  204.             import dialogs
  205.             _ = gettext
  206.             import gtcache
  207.             if not ignoreErrors:
  208.                 chkboxdialog = dialogs.CheckboxTextboxDialog(_('Internal Error'), _('Miro has encountered an internal error. You can help us track down this problem and fix it by submitting an error report.'), _('Include entire program database including all video and channel metadata with crash report'), False, _('Describe what you were doing that caused this error'), dialogs.BUTTON_SUBMIT_REPORT, dialogs.BUTTON_IGNORE)
  209.                 (chkboxdialog.run,)((lambda x: _sendReport(report, x)))
  210.         except Exception:
  211.             e = None
  212.             logging.exception('Execption when reporting errror..')
  213.         except:
  214.             None<EXCEPTION MATCH>Exception
  215.         
  216.  
  217.     None<EXCEPTION MATCH>Exception
  218.     command = command
  219.     daemon = daemon
  220.     import dl_daemon
  221.     c = command.DownloaderErrorCommand(daemon.lastDaemon, report)
  222.     c.send()
  223.  
  224.  
  225. def _sendReport(report, dialog):
  226.     global ignoreErrors
  227.     
  228.     def callback(result):
  229.         app.controller.sendingCrashReport -= 1
  230.         if result['status'] != 200 or result['body'] != 'OK':
  231.             logging.warning(u'Failed to submit crash report. Server returned %r' % result)
  232.         else:
  233.             logging.info(u'Crash report submitted successfully')
  234.  
  235.     
  236.     def errback(error):
  237.         app.controller.sendingCrashReport -= 1
  238.         logging.warning(u'Failed to submit crash report %r' % error)
  239.  
  240.     import dialogs
  241.     import httpclient
  242.     import config
  243.     import prefs
  244.     import app
  245.     if dialog.choice == dialogs.BUTTON_IGNORE:
  246.         ignoreErrors = True
  247.         return None
  248.     
  249.     backupfile = None
  250.     if hasattr(dialog, 'checkbox_value') and dialog.checkbox_value:
  251.         
  252.         try:
  253.             logging.info('Sending entire database')
  254.             import database
  255.             backupfile = database.defaultDatabase.liveStorage.backupDatabase()
  256.         traceback.print_exc()
  257.         logging.warning(u'Failed to backup database')
  258.  
  259.     
  260.     description = u'Description text not implemented'
  261.     if hasattr(dialog, 'textbox_value'):
  262.         description = dialog.textbox_value
  263.     
  264.     description = description.encode('utf-8')
  265.     postVars = {
  266.         'description': description,
  267.         'app_name': config.get(prefs.LONG_APP_NAME),
  268.         'log': report }
  269.     if backupfile:
  270.         postFiles = {
  271.             'databasebackup': {
  272.                 'filename': 'databasebackup.zip',
  273.                 'mimetype': 'application/octet-stream',
  274.                 'handle': open(backupfile, 'rb') } }
  275.     else:
  276.         postFiles = None
  277.     app.controller.sendingCrashReport += 1
  278.     httpclient.grabURL('http://participatoryculture.org/bogondeflector/index.php', callback, errback, method = 'POST', postVariables = postVars, postFiles = postFiles)
  279.  
  280.  
  281. class AutoflushingStream:
  282.     '''Converts a stream to an auto-flushing one.  It behaves in exactly the
  283.     same way, except all write() calls are automatically followed by a
  284.     flush().
  285.     '''
  286.     
  287.     def __init__(self, stream):
  288.         self.__dict__['stream'] = stream
  289.  
  290.     
  291.     def write(self, data):
  292.         if isinstance(data, unicode):
  293.             data = data.encode('ascii', 'backslashreplace')
  294.         
  295.         self.stream.write(data)
  296.         self.stream.flush()
  297.  
  298.     
  299.     def __getattr__(self, name):
  300.         return getattr(self.stream, name)
  301.  
  302.     
  303.     def __setattr__(self, name, value):
  304.         return setattr(self.stream, name, value)
  305.  
  306.  
  307.  
  308. def makeDummySocketPair():
  309.     '''Create a pair of sockets connected to each other on the local
  310.     interface.  Used to implement SocketHandler.wakeup().
  311.     '''
  312.     dummy_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  313.     dummy_server.bind(('127.0.0.1', 0))
  314.     dummy_server.listen(1)
  315.     server_address = dummy_server.getsockname()
  316.     first = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  317.     first.connect(server_address)
  318.     (second, address) = dummy_server.accept()
  319.     dummy_server.close()
  320.     return (first, second)
  321.  
  322.  
  323. def trapCall(when, function, *args, **kwargs):
  324.     '''Make a call to a function, but trap any exceptions and do a failedExn
  325.     call for them.  Return True if the function successfully completed, False
  326.     if it threw an exception
  327.     '''
  328.     
  329.     try:
  330.         function(*args, **kwargs)
  331.         return True
  332.     except KeyboardInterrupt:
  333.         raise 
  334.     except:
  335.         failedExn(when)
  336.         return False
  337.  
  338.  
  339. TRACK_CUMULATIVE = False
  340. cumulative = { }
  341. cancel = False
  342.  
  343. def timeTrapCall(when, function, *args, **kwargs):
  344.     global cancel, cancel
  345.     cancel = False
  346.     start = clock()
  347.     retval = trapCall(when, function, *args, **kwargs)
  348.     end = clock()
  349.     if cancel:
  350.         return retval
  351.     
  352.     if end - start > 1:
  353.         logging.timing('WARNING: %s too slow (%.3f secs)', when, end - start)
  354.     
  355.     if TRACK_CUMULATIVE:
  356.         
  357.         try:
  358.             total = cumulative[when]
  359.         except KeyboardInterrupt:
  360.             raise 
  361.         except:
  362.             total = 0
  363.  
  364.         total += end - start
  365.         cumulative[when] = total
  366.         return retval
  367.         if total > 5:
  368.             logging.timing('%s cumulative is too slow (%.3f secs)', when, total)
  369.             cumulative[when] = 0
  370.         
  371.     
  372.     cancel = True
  373.     return retval
  374.  
  375.  
  376. def getTorrentInfoHash(path):
  377.     f = open(path, 'rb')
  378.     
  379.     try:
  380.         data = f.read()
  381.         metainfo = bdecode(data)
  382.         infohash = sha.sha(bencode(metainfo['info'])).digest()
  383.         return infohash
  384.     finally:
  385.         f.close()
  386.  
  387.  
  388.  
  389. class ExponentialBackoffTracker:
  390.     '''Utility class to track exponential backoffs.'''
  391.     
  392.     def __init__(self, baseDelay):
  393.         self.baseDelay = self.currentDelay = baseDelay
  394.  
  395.     
  396.     def nextDelay(self):
  397.         rv = self.currentDelay
  398.         self.currentDelay *= 2
  399.         return rv
  400.  
  401.     
  402.     def reset(self):
  403.         self.currentDelay = self.baseDelay
  404.  
  405.  
  406.  
  407. def gatherVideos(path, progressCallback):
  408.     import item
  409.     import prefs
  410.     import config
  411.     import platformutils
  412.     keepGoing = True
  413.     parsed = 0
  414.     found = list()
  415.     
  416.     try:
  417.         for root, dirs, files in os.walk(path):
  418.             for f in files:
  419.                 parsed = parsed + 1
  420.                 if filetypes.isVideoFilename(f):
  421.                     found.append(os.path.join(root, f))
  422.                 
  423.                 if parsed > 1000:
  424.                     adjustedParsed = int(parsed / 100) * 100
  425.                 elif parsed > 100:
  426.                     adjustedParsed = int(parsed / 10) * 10
  427.                 else:
  428.                     adjustedParsed = parsed
  429.                 keepGoing = progressCallback(adjustedParsed, len(found))
  430.                 if not keepGoing:
  431.                     found = None
  432.                     raise 
  433.                     continue
  434.             
  435.             if config.get(prefs.SHORT_APP_NAME) in dirs:
  436.                 dirs.remove(config.get(prefs.SHORT_APP_NAME))
  437.                 continue
  438.     except KeyboardInterrupt:
  439.         raise 
  440.     except:
  441.         pass
  442.  
  443.     return found
  444.  
  445.  
  446. def formatSizeForUser(bytes, zeroString = '', withDecimals = True, kbOnly = False):
  447.     '''Format an int containing the number of bytes into a string suitable for
  448.     printing out to the user.  zeroString is the string to use if bytes == 0.
  449.     '''
  450.     _ = gettext
  451.     import gtcache
  452.     if bytes > 1073741824 and not kbOnly:
  453.         value = bytes / 1.07374e+09
  454.         if withDecimals:
  455.             format = _('%1.1fGB')
  456.         else:
  457.             format = _('%dGB')
  458.     elif bytes > 1048576 and not kbOnly:
  459.         value = bytes / 1.04858e+06
  460.         if withDecimals:
  461.             format = _('%1.1fMB')
  462.         else:
  463.             format = _('%dMB')
  464.     elif bytes > 1024:
  465.         value = bytes / 1024
  466.         if withDecimals:
  467.             format = _('%1.1fKB')
  468.         else:
  469.             format = _('%dKB')
  470.     elif bytes > 1:
  471.         value = bytes
  472.         if withDecimals:
  473.             format = _('%1.1fB')
  474.         else:
  475.             format = _('%dB')
  476.     else:
  477.         return zeroString
  478.     return format % value
  479.  
  480.  
  481. def formatTimeForUser(seconds, sign = 1):
  482.     """Format a duration in seconds into a string suitable for display, using
  483.     the minimum amount of digits. Negative durations used for remaining times
  484.     display a '-' sign.
  485.     """
  486.     (_, _, _, h, m, s, _, _, _) = time.gmtime(seconds)
  487.     if sign < 0:
  488.         sign = '-'
  489.     else:
  490.         sign = ''
  491.     if int(seconds) in range(0, 3600):
  492.         return '%s%d:%02u' % (sign, m, s)
  493.     else:
  494.         return '%s%d:%02u:%02u' % (sign, h, m, s)
  495.  
  496.  
  497. def makeAnchor(label, href):
  498.     return '<a href="%s">%s</a>' % (href, label)
  499.  
  500.  
  501. def makeEventURL(label, eventURL):
  502.     return '<a href="#" onclick="return eventURL(\'action:%s\');">%s</a>' % (eventURL, label)
  503.  
  504.  
  505. def clampText(text, maxLength):
  506.     if len(text) > maxLength:
  507.         return text[:maxLength - 3] + '...'
  508.     else:
  509.         return text
  510.  
  511.  
  512. def print_mem_usage(message):
  513.     pass
  514.  
  515.  
  516. class TooManySingletonsError(Exception):
  517.     pass
  518.  
  519.  
  520. def getSingletonDDBObject(view):
  521.     view.confirmDBThread()
  522.     viewLength = view.len()
  523.     if viewLength == 1:
  524.         view.resetCursor()
  525.         return view.next()
  526.     elif viewLength == 0:
  527.         raise LookupError("Can't find singleton in %s" % repr(view))
  528.     else:
  529.         msg = '%d objects in %s' % (viewLength, len(view))
  530.         raise TooManySingletonsError(msg)
  531.  
  532.  
  533. class ThreadSafeCounter:
  534.     '''Implements a counter that can be access by multiple threads.'''
  535.     
  536.     def __init__(self, initialValue = 0):
  537.         self.value = initialValue
  538.         self.lock = threading.Lock()
  539.  
  540.     
  541.     def inc(self):
  542.         self.lock.acquire()
  543.         
  544.         try:
  545.             self.value += 1
  546.         finally:
  547.             self.lock.release()
  548.  
  549.  
  550.     
  551.     def dec(self):
  552.         self.lock.acquire()
  553.         
  554.         try:
  555.             self.value -= 1
  556.         finally:
  557.             self.lock.release()
  558.  
  559.  
  560.     
  561.     def getvalue(self):
  562.         self.lock.acquire()
  563.         
  564.         try:
  565.             return self.value
  566.         finally:
  567.             self.lock.release()
  568.  
  569.  
  570.  
  571.  
  572. def setupLogging():
  573.     logging.addLevelName(25, 'TIMING')
  574.     
  575.     logging.timing = lambda msg, *args, **kargs: logging.log(25, msg, *args, **kargs)
  576.     logging.addLevelName(26, 'JSALERT')
  577.     
  578.     logging.jsalert = lambda msg, *args, **kargs: logging.log(26, msg, *args, **kargs)
  579.  
  580.  
  581. class DemocracyUnicodeError(StandardError):
  582.     pass
  583.  
  584.  
  585. def checkU(text):
  586.     if text is not None and type(text) != UnicodeType:
  587.         raise DemocracyUnicodeError, u'text "%s" is not a unicode string' % text
  588.     
  589.  
  590.  
  591. def returnsUnicode(func):
  592.     
  593.     def checkFunc(*args, **kwargs):
  594.         result = func(*args, **kwargs)
  595.         if result is not None:
  596.             checkU(result)
  597.         
  598.         return result
  599.  
  600.     return checkFunc
  601.  
  602.  
  603. def checkB(text):
  604.     if text is not None and type(text) != StringType:
  605.         raise DemocracyUnicodeError, u'text "%s" is not a binary string' % text
  606.     
  607.  
  608.  
  609. def returnsBinary(func):
  610.     
  611.     def checkFunc(*args, **kwargs):
  612.         result = func(*args, **kwargs)
  613.         if result is not None:
  614.             checkB(result)
  615.         
  616.         return result
  617.  
  618.     return checkFunc
  619.  
  620.  
  621. def checkURL(text):
  622.     if type(text) != UnicodeType:
  623.         raise DemocracyUnicodeError, u'url "%s" is not unicode' % text
  624.     
  625.     
  626.     try:
  627.         text.encode('ascii')
  628.     except:
  629.         raise DemocracyUnicodeError, u'url "%s" contains extended characters' % text
  630.  
  631.  
  632.  
  633. def returnsURL(func):
  634.     
  635.     def checkFunc(*args, **kwargs):
  636.         result = func(*args, **kwargs)
  637.         if result is not None:
  638.             checkURL(result)
  639.         
  640.         return result
  641.  
  642.     return checkFunc
  643.  
  644.  
  645. def checkF(text):
  646.     FilenameType = FilenameType
  647.     import platformutils
  648.     if text is not None and type(text) != FilenameType:
  649.         raise DemocracyUnicodeError, u'text "%s" is not a valid filename type' % text
  650.     
  651.  
  652.  
  653. def returnsFilename(func):
  654.     
  655.     def checkFunc(*args, **kwargs):
  656.         result = func(*args, **kwargs)
  657.         if result is not None:
  658.             checkF(result)
  659.         
  660.         return result
  661.  
  662.     return checkFunc
  663.  
  664.  
  665. def unicodify(d):
  666.     '''Turns all strings in data structure to unicode.
  667.     '''
  668.     if isinstance(d, dict):
  669.         for key in d.keys():
  670.             d[key] = unicodify(d[key])
  671.         
  672.     elif isinstance(d, list):
  673.         for key in range(len(d)):
  674.             d[key] = unicodify(d[key])
  675.         
  676.     elif type(d) == StringType:
  677.         d = d.decode('ascii', 'replace')
  678.     
  679.     return d
  680.  
  681.  
  682. def stringify(u, handleerror = 'xmlcharrefreplace'):
  683.     '''Takes a possibly unicode string and converts it to a string string.
  684.     This is required for some logging especially where the things being
  685.     logged are filenames which can be Unicode in the Windows platform.
  686.  
  687.     Note that this is not the inverse of unicodify.
  688.  
  689.     You can pass in a handleerror argument which defaults to "xmlcharrefreplace".
  690.     This will increase the string size as it converts unicode characters that
  691.     don\'t have ascii equivalents into escape sequences.  If you don\'t want to
  692.     increase the string length, use "replace" which will use ? for unicode
  693.     characters that don\'t have ascii equivalents.
  694.     '''
  695.     if isinstance(u, unicode):
  696.         return u.encode('ascii', handleerror)
  697.     
  698.     if not isinstance(u, str):
  699.         return str(u)
  700.     
  701.     return u
  702.  
  703.  
  704. def quoteUnicodeURL(url):
  705.     '''Quote international characters contained in a URL according to w3c, see:
  706.     <http://www.w3.org/International/O-URL-code.html>
  707.     '''
  708.     checkU(url)
  709.     quotedChars = list()
  710.     for c in url.encode('utf8'):
  711.         if ord(c) > 127:
  712.             quotedChars.append(urllib.quote(c))
  713.             continue
  714.         quotedChars.append(c)
  715.     
  716.     return u''.join(quotedChars)
  717.  
  718.  
  719. def no_console_startupinfo():
  720.     """Returns the startupinfo argument for subprocess.Popen so that we don't
  721.     open a console window.  On platforms other than windows, this is just
  722.     None.  On windows, it's some win32 sillyness.
  723.     """
  724.     if subprocess.mswindows:
  725.         startupinfo = subprocess.STARTUPINFO()
  726.         startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
  727.         return startupinfo
  728.     else:
  729.         return None
  730.  
  731.  
  732. def call_command(*args, **kwargs):
  733.     """Call an external command.  If the command doesn't exit with status 0,
  734.     or if it outputs to stderr, an exception will be raised.  Returns stdout.
  735.     """
  736.     ignore_stderr = kwargs.pop('ignore_stderr', False)
  737.     if kwargs:
  738.         raise TypeError('extra keyword arguments: %s' % kwargs)
  739.     
  740.     pipe = subprocess.Popen(args, stdout = subprocess.PIPE, stdin = subprocess.PIPE, stderr = subprocess.PIPE, startupinfo = no_console_startupinfo())
  741.     (stdout, stderr) = pipe.communicate()
  742.     if pipe.returncode != 0:
  743.         raise OSError('call_command with %s has return code %s\nstdout:%s\nstderr:%s' % (args, pipe.returncode, stdout, stderr))
  744.     elif stderr and not ignore_stderr:
  745.         raise OSError('call_command with %s outputed error text:\n%s' % (args, stderr))
  746.     else:
  747.         return stdout
  748.  
  749.  
  750. def getsize(path):
  751.     """Get the size of a path.  If it's a file, return the size of the file.
  752.     If it's a directory return the total size of all the files it contains.
  753.     """
  754.     if os.path.isdir(path):
  755.         size = 0
  756.         for dirpath, dirnames, filenames in os.walk(path):
  757.             for name in filenames:
  758.                 size += os.path.getsize(os.path.join(dirpath, name))
  759.             
  760.             size += os.path.getsize(dirpath)
  761.         
  762.         return size
  763.     else:
  764.         return os.path.getsize(path)
  765.  
  766.  
  767. def partition(list, size):
  768.     '''Partiction list into smaller lists such that none is larger than
  769.     size elements.
  770.  
  771.     Returns a list of lists.  The lists appended together will be the original
  772.     list.
  773.     '''
  774.     retval = []
  775.     for start in range(0, len(list), size):
  776.         retval.append(list[start:start + size])
  777.     
  778.     return retval
  779.  
  780.  
  781. def directoryWritable(directory):
  782.     '''Check if we can write to a directory.'''
  783.     
  784.     try:
  785.         f = tempfile.TemporaryFile(dir = directory)
  786.     except OSError:
  787.         return False
  788.  
  789.     f.close()
  790.     return True
  791.  
  792.  
  793. def random_string(length):
  794.     return ''.join((lambda .0: for i in .0:
  795. random.choice(string.ascii_letters))(xrange(length)))
  796.  
  797.